﻿using System;
using Faml.Frontend;
using System.Linq.Expressions;
using Faml.Optimization;
using Faml.LinqCompiler;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using Faml.Compiler;

namespace Faml
{

    public class RuntimeEnvironment
    {
        private OperationLibrary opLibrary; 
        private MergeTable mergeTable;
        private List<IVisitor> visitors;
        private Faml.Frontend.Compiler precompiler;
        private static int assemblies = 0;

        public RuntimeEnvironment()
        {
            opLibrary = new OperationLibrary(GenericInstanceProvider);
            mergeTable = new MergeTable();
            visitors = new List<IVisitor>();

            precompiler = new Faml.Frontend.Compiler(opLibrary);
        }

        private RuntimeEnvironment(RuntimeEnvironment re)
        {
            this.opLibrary = OperationLibrary.CreateFrom(re.opLibrary);            
            this.mergeTable = re.mergeTable;

            this.visitors = new List<IVisitor>();

            IVisitor mergeVisitor = new MergeVisitor(this);
            IVisitor getlensubVisitor = new GetLengthSubarrayVisitor();

            this.visitors.Add(getlensubVisitor);
            this.visitors.Add(mergeVisitor);

            this.precompiler = new Faml.Frontend.Compiler(this.opLibrary);

            OperationInstanceProvider = GenericInstanceProvider;
        }

        private object GenericInstanceProvider(MethodInfo method)
        {
            throw new ApplicationException("Operation instance provider is not specified");
        }

        public OperationInstanceProvider OperationInstanceProvider
        {
            set
            {
                opLibrary.OperationInstanceProvider = value;
            }
            get
            {
                return opLibrary.OperationInstanceProvider;
            }
        }

        public FamlExpression Precompile(Parser mp)
        {
            return precompiler.Precompile(mp);
        }

        public static RuntimeEnvironment CreateFrom(RuntimeEnvironment runtimeEnvironment)
        {
            RuntimeEnvironment re = new RuntimeEnvironment(runtimeEnvironment);
            return re;
        }

        public static RuntimeEnvironment GetDefaultRuntimeEnvironment()
        {
            RuntimeEnvironment re = new RuntimeEnvironment();

            re.AddAssembly("Faml.Sources.dll");
            re.AddAssembly(typeof(Operation).Assembly); //"Faml.Base.dll"

            IVisitor mergeVisitor = new MergeVisitor(re);
            IVisitor getlensubVisitor = new GetLengthSubarrayVisitor();

            re.visitors.Add(getlensubVisitor);
            re.visitors.Add(mergeVisitor);

            return re;
        }

        public List<IVisitor> Visitors
        {
            get { return visitors; }
        }

        public MergeTable MergeTable
        {
            get { return mergeTable; }
        }

        public OperationLibrary OperationLibrary
        {
            get { return opLibrary; }
        }

        public int OperationCount
        {
            get
            {
                return opLibrary.GetOperationsNumber();
            }
        }

        public int AssemblyCount
        {
            get
            {
                return assemblies;
            }
        }

        public PrecompilerContext Context
        {
            get
            {
                return precompiler.Context;
            }
        }

        public Faml.Frontend.Compiler Compiler
        {
            get
            {
                return precompiler;
            }
        }

        public void AddAssembly(string assemblyName)
        {
            try
            {
                opLibrary.AddMethods(assemblyName);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
            try
            {
                mergeTable.AddOptimizationPairs(Assembly.LoadFrom(assemblyName));
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }

            assemblies++;
        }

        public void AddAssembly(Assembly assembly)
        {
            try
            {
                opLibrary.AddMethods(assembly);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
            try
            {
                mergeTable.AddOptimizationPairs(assembly);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }

            assemblies++;
        }

    }

}